package pool.dbpool ;

import java.sql.Connection ;
import java.sql.DatabaseMetaData ;
import java.sql.Driver ;
import java.sql.DriverManager ;
import java.sql.SQLException ;
import java.util.Enumeration ;
import java.util.Vector ;

/**
 * @author 80002165 @date 2017年7月14日 下午6:43:23
 */
public class ConnectionPool {
    private String jdbcDriver ;
    private String dbUrl ;
    private String username ;
    private String password ;
    
    // 连接池初始大小
    private int initialConnections = 10 ;
    // 连接池自动增加的大小
    private int incrementalConnections = 5 ;
    // 连接池最大值
    private int maxConnections = 50 ;
    // 存放连接池中数据库连接的向量，初始为null
    private Vector<PooledConnection> connections = null ;
    
    public ConnectionPool(String jdbcDriver, String dbUrl, String dbUsername, String dbPassword) {
        this.jdbcDriver = jdbcDriver ;
        this.dbUrl = dbUrl ;
        this.username = dbUsername ;
        this.password = dbPassword ;
    }
    
    public String getJdbcDriver() {
        return jdbcDriver ;
    }
    
    public void setJdbcDriver(String jdbcDriver) {
        this.jdbcDriver = jdbcDriver ;
    }
    
    public String getDbUrl() {
        return dbUrl ;
    }
    
    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl ;
    }
    
    public String getUsername() {
        return username ;
    }
    
    public void setUsername(String username) {
        this.username = username ;
    }
    
    public String getPassword() {
        return password ;
    }
    
    public void setPassword(String password) {
        this.password = password ;
    }
    
    public int getInitialConnections() {
        return initialConnections ;
    }
    
    public void setInitialConnections(int initialConnections) {
        this.initialConnections = initialConnections ;
    }
    
    public int getIncrementalConnections() {
        return incrementalConnections ;
    }
    
    public void setIncrementalConnections(int incrementalConnections) {
        this.incrementalConnections = incrementalConnections ;
    }
    
    public int getMaxConnections() {
        return maxConnections ;
    }
    
    public void setMaxConnections(int maxConnections) {
        this.maxConnections = maxConnections ;
    }
    
    public synchronized void createPool() {
        if (connections != null)
            return ;
        // 实例化Driver
        Driver driver ;
        try {
            driver = (Driver) Class.forName(this.jdbcDriver).newInstance() ;
            DriverManager.registerDriver(driver) ;
            connections = new Vector<PooledConnection>() ;
            createConnection(this.initialConnections);
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {
            e.printStackTrace() ;
            System.out.println("创建连接池失败，无法创建数据库驱动" + this.jdbcDriver) ;
        }
        
    }
    
    /**
     * 初始化连接池
     * @author 80002165 @date 2017年7月14日 下午7:25:54
     * @param numConnections
     */
    private void createConnection(int numConnections) {
        for (int num = 0; num < numConnections; num++) {
            // 连接池中的连接不可超过最大数
            if (this.maxConnections > 0 && this.connections.size() >= this.maxConnections)
                break ;
            Connection connection = DBConnectionUtil.getConnection(this.jdbcDriver, this.dbUrl, this.username, this.password) ;
            if (connection == null)
                throw new RuntimeException("初始化连接池出错！无法创建数据库连接连接对象！") ;
            connections.addElement(new PooledConnection(connection)) ;
        }
    }
    
    /**
     * 返回一个数据库连接对象
     * @author 80002165 @date 2017年7月14日 下午7:26:19
     * @return
     * @throws SQLException 
     */
    public Connection newConnection() throws SQLException {
        // 创建一个数据库连接
        Connection conn = DriverManager.getConnection(this.dbUrl, this.username, this.password) ;
        // 如果这是第一次创建数据库连接，即检查数据库，获得此数据库允许支持的
        // 最大客户连接数目
        // connections.size()==0 表示目前没有连接己被创建
        if (connections.size() == 0) {
            DatabaseMetaData metaData = conn.getMetaData() ;
            int driverMaxConnections = metaData.getMaxConnections() ;
            // 数据库返回的 driverMaxConnections 若为 0 ，表示此数据库没有最大
            // 连接限制，或数据库的最大连接限制不知道
            // driverMaxConnections 为返回的一个整数，表示此数据库允许客户连接的数目
            // 如果连接池中设置的最大连接数量大于数据库允许的连接数目 , 则置连接池的最大
            // 连接数目为数据库允许的最大数目
            if (driverMaxConnections > 0 && this.maxConnections > driverMaxConnections) {
                this.maxConnections = driverMaxConnections ;
            }
        }
        return conn ; // 返回创建的新的数据库连接
    }
    
    /**
     * 返回连接池中一个可用的数据库连接，如果没有可用的数据库连接，并且更多的数据库连接不能创建（连接池大小限制），次函数等待一会再尝试获取
     * 如果使用connection没有释放，则会查询连接池中的下一个connection。
     * 如果使用connection时，释放。则每次都会用第一个connection
     * 以上测试在单线程环境下执行
     * @author 80002165 @date 2017年7月14日 下午7:32:37
     * @return
     * @throws SQLException 
     */
    public synchronized Connection getConnection() throws SQLException {
        // 连接池未创建
        if (connections == null) {
            return null ;
        }
        
        // 获取未使用的连接
        Connection connection = getFreeConnection() ;
        
        int retry = 0 ;
        while (connection == null) {
            if (retry == 5)
                return null ;
            wait(250) ;
            connection = getFreeConnection() ;
            retry++ ;
        }
        return connection ;
    }
    
    /**
     * 从向量池中返回一个可用的数据库连接
     * @author 80002165 @date 2017年7月14日 下午7:36:33
     * @return
     * @throws SQLException 
     */
    private Connection getFreeConnection() throws SQLException {
        Connection connection = findFreeConnection() ;
        if (connection == null) {
            createConnection(incrementalConnections) ;
            connection = findFreeConnection() ;
        }
        
        return connection ;
    }
    
    /**
     * 从连接池中查找可用的数据库连接
     * @author 80002165 @date 2017年7月14日 下午7:38:38
     * @return
     * @throws SQLException 
     */
    private Connection findFreeConnection() throws SQLException {
        Connection connection = null ;
        PooledConnection pConnection = null ;
        Enumeration<PooledConnection> enumeration = connections.elements() ;
        while (enumeration.hasMoreElements()) {
            pConnection = enumeration.nextElement() ;
            if (!pConnection.isBusy()) {
                connection = pConnection.getConnection() ;
                pConnection.setBusy(true) ;
                // 数据库连接测试无效，新建一个连接替换
                if (!testConnection(connection)) {
                    connection = newConnection() ;
                    if (connection == null)
                        return null ;
                }
                pConnection.setConnection(connection) ;
                break ;
            }
        }
        return connection ;
    }
    
    /**
     * 测试当前Connection是否可用
     * @author 80002165 @date 2017年7月14日 下午7:44:37
     * @param connection
     * @return
     */
    private boolean testConnection(Connection connection) {
        // 当表不存在时，创建测试表
        
        return true ;
    }
    
    /**
     * 释放数据库连接
     * @author 80002165 @date 2017年7月14日 下午7:48:40
     * @param connection
     */
    public void releaseConnection(Connection connection) {
        // 确保连接池存在，如果连接没有创建（不存在），直接返回
        if (connections == null) {
            System.out.println(" 连接池不存在，无法返回此连接到连接池中 !") ;
            return ;
        }
        
        PooledConnection pConnection = null ;
        Enumeration<PooledConnection> enumeration = connections.elements() ;
        while (enumeration.hasMoreElements()) {
            pConnection = enumeration.nextElement() ;
            if (pConnection.getConnection() == connection) {
                pConnection.setBusy(false) ;
                break ;
            }
        }
    }
    
    /**
     * 刷新连接池中所有的连接对象
     * 
     */
    
    public synchronized void refreshConnections() throws SQLException {
        // 确保连接池己创新存在
        if (connections == null) {
            System.out.println(" 连接池不存在，无法刷新 !") ;
            return ;
        }
        PooledConnection pConn = null ;
        /**
         * 这里考虑使用多线程提高效率
         */
        Enumeration<PooledConnection> enumerate = connections.elements() ;
        while (enumerate.hasMoreElements()) {
            // 获得一个连接对象
            pConn = (PooledConnection) enumerate.nextElement() ;
            // 如果对象忙则等 5 秒 ,5 秒后直接刷新
            if (pConn.isBusy()) {
                wait(5000) ; // 等 5 秒
            }
            // 关闭此连接，用一个新的连接代替它。
            closeConnection(pConn.getConnection()) ;
            pConn.setConnection(newConnection()) ;
            pConn.setBusy(false) ;
        }
    }
    
    /**
     * 关闭连接池中所有的连接，并清空连接池。
     */
    
    public synchronized void closeConnectionPool() throws SQLException {
        // 确保连接池存在，如果不存在，返回
        if (connections == null) {
            System.out.println(" 连接池不存在，无法关闭 !") ;
            return ;
        }
        PooledConnection pConn = null ;
        Enumeration<PooledConnection> enumerate = connections.elements() ;
        while (enumerate.hasMoreElements()) {
            pConn = (PooledConnection) enumerate.nextElement() ;
            // 如果忙，等 5 秒
            if (pConn.isBusy()) {
                wait(5000) ; // 等 5 秒
            }
            // 5 秒后直接关闭它
            closeConnection(pConn.getConnection()) ;
            // 从连接池向量中删除它
            connections.removeElement(pConn) ;
        }
        // 置连接池为空
        connections = null ;
    }
    
    /**
     * 关闭一个数据库连接
     * 
     * @param 需要关闭的数据库连接
     */
    
    private void closeConnection(Connection conn) {
        try {
            conn.close() ;
        } catch (SQLException e) {
            System.out.println(" 关闭数据库连接出错： " + e.getMessage()) ;
        }
    }
    
    /**
     * 使程序等待给定的毫秒数
     * 
     * @param 给定的毫秒数
     */
    
    private void wait(int mSeconds) {
        try {
            Thread.sleep(mSeconds) ;
        } catch (InterruptedException e) {
        }
    }
}
